Generics Programming
Introduction
JDK 5 has introduced a new syntactical element called generics. Today, generics are an integral part of Java programming, and a solid understanding of this important feature is required. It is examined here in detail.
Why Generics?
Through the use of generics, it is possible to create classes, interfaces, and methods that will work in a type-safe manner with various kinds of data. Many algorithms are logically the same no matter what type of data they are being applied to. For example, the mechanism that supports a stack is the same whether that stack is storing items of type Integer, String, Object, or Thread. With generics, you can define an algorithm once, independently of any specific type of data, and then apply that algorithm to a wide variety of data types without any additional effort. The expressive power generics added to the language fundamentally changed the way that Java code is written.
Generics versus Collection Framework
The Collections Framework is part of the Java API and is described in detail in in the next module, but a brief mention is useful now. A collection is a group of objects. The Collections framework defines several classes, such as List, Array, Map, etc. that manage collections. The collection classes have always been able to work with any type of object. The benefit that generics added is that the collection classes can now be used with complete type safety. Thus, in addition to being a powerful language element on its own, generics also enabled an existing feature to be substantially improved. This is another reason why generics were such an important addition to Java.
Chapter plan
This chapter describes the syntax, theory, and use of generics. It also shows how generics provide type safety for some previously difficult cases. This will follows the discussion of the Collections Framework. There you will find many examples of generics at work.
Generic
methods
A
method in a class that can take any parameter is called a generic method.
Suppose, in a class we want to define a method which can take any argument to
display its content.
Example 2.1.
//
A simple Java program to show working of a generic method.
class
GenericMethod {
// Defining a generic method to print any
data type
void genericPrint (T t) {
System.out.println (t);
}
public static void main(String[] args) {
GenericMethod aObj; // Creating an object of the class GenericMethod
// Calling generic method with Integer
argument
aObj.genericDisplay(101);
// Calling generic method with String
argument
aObj.genericDisplay("Joy with
Java");
// Calling generic method with double
argument
aObj.genericDisplay(3.1412343);
}
}
Note:
·
You can readily understand the similarity between method
overriding and generic method. Both the concepts have the same objective, but
in their own ways. The main difference is that in case of method overriding, we
have to build code for each overridden method, whereas, with generic method,
same code can work for the different types of data.
·
Further, with generic method, theoretically you can pass
any type of data as argument; however, with method overriding only a limited number
of arguments are allowed.
·
According to the class encapsulation, method overloading
and method overriding are also applicable to generic methods.
Example 2.2.
The
following program to define a static generic method in a class.
//
A simple Java program to show working of a generic method.
class
GenericStaticMethod {
// Defining a static generic method to
print any data type
Static <T> void genericPrint (T
t) {
// The following statement print any type
specified by the parameter T
System.out.println (t.getClass().getName()
“:” + t);
}
public static void main(String[] args) {
// GenericMethod aObj;
// Creating an object of the class
GenericMethod
// Calling generic method with Integer
argument
genericDisplay(101);
// Calling generic method with String
argument
genericDisplay("Joy with
Java");
// Calling generic method with double
argument
genericDisplay(3.1412343);
}
}
If you are a C programmer, then you have noticed that in C, there are some standard methods like printf(…), scanf(…), which takes variable number of arguments. Further, in a C program, you can define a method with variable number of arguments to be passed. It is intersecting to know if you can do the similar things or not in Java!
Obviously, one solution you may think is to write as many as overloading methods. Indeed, this is a naive idea. Fortunately, there are some elegant ideas Java developer provides you. With their idea, you can write a method so that it will accept an arbitrary number of arguments when it is called. This feature in Java is called varargs method also called variable‐arity method. There are three ways:
1. Using an array
2. Using ellipsis (three dots)
3. Using Object class
Let us examine
each idea in the following
discussions.
This is the simplest
method. Here, you can define a varargs
method with an argument an array (of any type). In other words, the values which you want to pass to a method, store them in
an array and then pass the array to the method.
That’s all! This approach
is illustrated in Program 2.3.
Example 2.3.
/* This program demonstrates a varargs
method using an array to pass a variable number of arguments to a method. */
class
VarargsMethod1 {
static void varargsMethod1(int v[]) {
System.out.print("Number of args:
" + v.length +" Elements: ");
for(int x : v)
System.out.print(x + "
");
System.out.println();
}
public static void main(String args[]) {
// Following arrays are created for
test...
int x[] = { 1, 3, 5, 7 };
int y[] = { 2, 4};
int z[] = { };
varargsMethod1 (x); // Passed 4
values to the method
varargsMethod1 (y); // Passed 4
values to the method
varargsMethod1 (y); // Passed no
argument to the method
}
}
The second method is similar to the method but with a different syntax, which is special in Java. The syntax to define varargs method with this approach is given below.
<AccessSpec><ReturnType><MethodName>(<Type>...<arrayName>) {
. . . // Method body
}
Example 2.4.
/* This program
demonstrates varargs method with ellipses. */
class VarargsMethod2
{
//Defining a varargs method
static void varargsMethod2(int ...v) {
System.out.println("Number
of arguments: " + v.length);
for (int i: v) // For each item i in
array v
System.out.print(i
+ " ");
System.out.println();
}
public static void main(String args[]) {
//
Calling the varargs method with variable arguments
varargsMethod2
(9); // One parameter
varargsMethod2
(1, -2, 3, -4); // Four parameters
varargsMethod2
(); // no parameter
}
}
.
· The two approaches need values to be packed into an array prior to calling.
· Command line arguments is also varargs method for the main() in your Java program.
· As it is an array to be passed, hence all values that can be passed is of same type.
· A method can have “normal” parameters along with a variable‐length parameter. However, the variable‐length parameter must be the last parameter declared by the method. For example, the following is a valid varargs method declaration.
.
int varMethod(int a, int b, double c, float ... v) { }
· In this case, the first three arguments used in a call to varMethod ( ) are matched to the first three parameters. Then, any remaining arguments are assumed to belong to v.
· You can overload a method that takes a variable‐length argument. Program 4.36 demonstrates how a varrags method can be overloaded.
Example 2.5.
/* This program demonstrates
the overloading of a varargs method. */
class OverloadingVarargs {
static void varTest(int ... v) { //Overloading method 1
System.out.print("Arg(int ...):
" + "Number of args: " +
v.length + " Contents: ");
for(int x : v)
System.out.print(x + "
");
System.out.println();
}
static void varTest(boolean ... v) { //Overloading method 2
System.out.print("Arg(boolean
...) " + "Number of args: " +
v.length + " Contents: ");
for(boolean x : v)
System.out.print(x + "
");
System.out.println();
}
static void varTest(String msg, int ...
v) { //Overloading method 3
System.out.print("Arg(String,
int ...): " + msg + v.length +
"
Contents: ");
for(int x : v)
System.out.print(x + "
");
System.out.println();
}
public static void main(String
args[]) {
varTest(1, 2, 3);
varTest("Testing: ", 10,
20);
varTest(true, false, false);
}
}
· The varargs parameter must be last. For example, the following declaration is incorrect:
int varMethod(int a, float ... v, double c) { }
· There is one more restriction to be aware of: there must be only one varargs parameter. For example, the following declaration is also invalid.
int varMethod (int ...a, double ...b) { }
· The attempt to declare the second varargs parameter is illegal.
· Sometime unexpected errors can result when overloading a method that takes a variable length argument. These errors involve ambiguity because it is possible to create an ambiguous call to an overloaded varargs method. For example, if you exclude overloaded method 3 and change the main() method as follows, then it will give the compilation error.
public static void main(String args[]) { varTest(1, 2, 3); // Okay
varTest(); // Compile time error
// as it does not match with anyone
varTest(true, false, false); // Okay
}
Possible this is the most elegant approach to implement the varargs method in your Java program. It used the ellipsis and in addition to this, it uses the Object type. For example, to define a varargs method your method declaration may take the following form.
public static void methodName(Object...obj) {
/
/ Body of the method
}
Further, you may note the restriction that the method can have zero or more parameters preceding this, but this must be the last for reasons you have already learned. Let’s consider Program 5 to demonstrate the mechanism. This program defines a varargs method which takes any type of values with any count.
Example 2.6.
/*
This program demonstrates the varargs method taking any type of arguments as
well as any number. */
class
VarargsMethod3 {
public static void varargsMethod3(Object
... obj) {
for(Object o : obj)
System.out.print(“ “+o);
System.out.println();
}
public static void main(String[] args) {
varargsMethod3( 1, “String”, 2.3,
true); // Four arguments
varargsMethod3 (); // No arguments
varargsMethod3 (15, 25, 35, 45,
55); // Five arguments
}
}
Generic
class
Let us consider
the case of an array type of numbers to be processed in a Java program. Let us
consider the case of initializing an array of integers and two methods of
printing the values and reversing the array elements. Here, is the program
which you should consider to define such a class.
class
SpecificArrayInt {
// Declaring an array of integer values
// Constructor to load the array.
// Method to print the array elements
// Method to reverse the array elements
}
class
MainClassInt {
//This class utilize the class
SpecificArrayInt to manipulate some integer data
}
Example 2.7:
//
Writing the full code here...
class
SpecificArrayInt {
// Declaring an array of integer values
int a;
// Constructor to load the array
SpecificArrayInt(int a[]) {
this.a = a;
}
// Method to print the array elements
void printInt() {
for(int x :
a)
System.out.println(x);
}
// Method to reverse the array elements
void reverseInt() {
j =
a.length;
for (int
i=0; i<j; i++)
int
temp;
temp =
a[i];
a[i]
= a[j];
a[j]
= temp;
j--;
}
}
}
class
MainClassInt {
//This class use the class
SpecificArrayInt to manipulate data in it
SpecificArrayInt a = {1, 2, 3, 4, 5};
a.printInt();
a.reverseInt();
a.print();
}
Now,
suppose, we want to manipulate double data. So, we have to redefine our
programs one again. This is shown below.
class
SpecificArrayDouble {
//Declaring an array of double values
// Constructor to load the array.
// Method to print the array elements
//Method to reverse the array elements
// Swap two elements in the array. It is static
class, say
}
class
MainClassDouble {
//This class utilize the class
SpecificArrayInt to manipulate some integer data
}
Example 2.8:
//
Writing the full code here...
class
SpecificArrayDouble {
// Declaring an array of integer values
double x;
// Constructor to load the array
SpecificArrayDouble(double x[]) {
this.x =
x;
}
// Method to print the array elements
void printDouble() {
for(int y :
x)
System.out.println(y);
}
// Method to reverse the array elements
void reverseDouble() {
j =
x.length;
for (int
i=0; i<j; i++)
double
temp;
temp =
a[i];
a[i] = a[j];
a[j] = temp;
j--;
}
}
}
class
MainClassDouble {
//This class use the class
SpecificArrayInt to manipulate data in it
SpecificArrayDouble x = {1.2, 2.3, 3.4,
4.5, 5.6};
x.printDouble();
x.reverseDouble();
x.print();
}
What we
should do, if we want to manipulate some names, instead of integer/ double
values? We can write another program, which is basically close replica of the
first two.
class
SpecificArrayString {
//Declaring an array of String objects
// Constructor to load the array.
// A generic method to print the array elements
//A generic method to reverse the array elements
}
Class
MainClassString {
//This
class utilize the class SpecificArrayInt to manipulate some integer data
}
Example 2.9:
//
Writing the full code here...
class
SpecificArrayString {
// Declaring an array of integer values
String s;
// Constructor to load the array
SpecificArrayString(String s[]) {
this.s =
s;
}
// Method to print the array elements
void printString() {
for(String
z : s)
System.out.println(z);
}
// Method to reverse the array elements
void reverseString() {
j =
s.length;
for (int
i=0; i<j; i++)
String
temp;
temp =
a[i];
a[i] = a[j];
a[j] = temp;
j--;
}
}
}
class
MainClassString {
//This class use the class
SpecificArrayInt to manipulate data in it
SpecificArrayString s = {“A”, “B”, “C”,
“D”, “E”};
s.printString();
s.reverseString();
s.print();
}
Solving the problems with generic class
A class that can refer to any type is known as a generic class. In other words, a generic class, is a class whose methods in it can deal with any type of object in it. Like C++, in Java, you should use < > to specify parameter types in generic class creation. The syntax for declaring a generic class is as follows:
[Access] class <ClassName> <<Type1> [, <Type2>, …]> {
… body of the class
}
Here is the full syntax for declaring a reference to a generic class and instance creation:
<className><typeList>
varName = new <className><typeList> (<InputArray>);
Example 2.10 gives a simple example
to define a generic class with one type
and its use in main class.
Example 2.10.
/* A Simple Java program to
show working of user defined generic class. */
class GenericClass<Type>
{ // Use <
> to specify class type
Type
obj; // An object of
type T is declared
GenericClass(Type
obj) { // Constructor of the
generic class
this.obj = obj;
}
public Type getObject() { // A Method in the class
return this.obj;
}
}
class GenericClassTest
{ // Driver class to test the
above
public static void main (String[] args) {
GenericClass
<Integer> iObj = new GenericClass
<Integer>(15);
// A class with
Integer type
System.out.println(iObj.getObject());
GenericClass
<String> sObj = new GenericClass
<String>("Java");
// Another class
with String type
System.out.println(sObj.getObject());
}
}
Example 2.11:
In the
above discussions, we have shared our programing experience, when we want to
process different type of data but using same logic or same application. The
generic feature in Java is a right solution to get rid-off code duplicities.
Example 8 is a solution with generic facility in Java.
class
GenericArray<T> {
//Declaring an array, which should store any type T
of data
T a[ ]; //
Define that the array a[ ] can store any type of data
GenericArray(T x) { // Define a constructor
a = x;
}
T getData(int i) {
// To
return the element stored in the i-th place in the array
return
a[i];
}
void printData (T b) { // A generic method to print the elements in array b
for(int i
= 0; i < b.length(); i ++)
System.out.print(b.getData(i) +
“ “); // Print the i-th element in b
System.out.println(); // Print a new line
}
void
reverseArray (T b) { //
Generic method to reversed the order of elements in array b
int
front = 0, rear = b.length-1;
while( front
< rear) {
temp = b[rear];
b[rear] = a[front];
a[front] = temp;
front++; rear--;
}
}
}
Class
GenericClassDemo {
public void static main(String args a[]) {
//Creating an array of integer data
Integer x[ ] = {10, 20, 30, 40,
50}; // It is an array of Integer
objects
// Store the data into generic array
GenericArray<Integer> arrayInt =
new GenericArray<Integer>(x);
//Alternatively:
// GenericArray<Integer> arrayInt =
new GenericArray<Integer>(new Integer x[ ] {10, 20, 30, 40, 50});
// Printing the data….
printData(arrayInt); // Printing the array of integer
objects
//Reverse ordering of data….
reverseArray(arrayInt);
// Printing the data after reverse
ordering….
printData(arrayInt); // Printing the array of integer
objects
// -----------------------------------------------------------------------
//Creating an array of String data
String y[ ] = {“A”, “B”, “C”}; // It is an array of String data
// Store the data into generic array
GenericArray<String> arrayString =
new GenericArray<String>(y);
// Printing the data….
printData(arrayString); // Printing the array of strings
//Reverse ordering of data….
reverseArray(arrayString);
// Printing the data after reverse
ordering….
printData(arrayString); // Printing the array of strings
//
----------------------------------------------------------------------
//Creating an array of float data
GenericArray<Double> arrayDouble =
new GenericArray<Double>(new Double[]);
//Creating an array of float data
Double z[ ] = {1.2, 2.3, 3.4,
4.5}; // It is an array of double
data
// Store the data into generic array
GenericArray<Double> arrayDouble =
new GenericArray<Double>(z);
// Printing the data….
printData(arrayDouble); // Printing the array of doubles
//Reverse ordering of data….
reverseArray(arrayDouble);
// Printing the data after reverse
ordering….
printData(arrayDouble); // Printing the array of doubles
}
}
Example 2.12.
Let us
start the discussion with a simple generic class declaration and it use.
Consider the following program, which define a generic class AnyData. The
AnyData class will include a data member
field, whose value can be of any type. Let the generic type be denoted
as T. Thus, AnyData <T> is the class with essential member in it. The class definition of AnyData followed by
the driver class using AnyData<T>
class is shown below.
public
class AnyData <T> {
// Two field of generic type T is defined
below
private T x;
// Constructor
public AnyData(T t) {
x = t;
}
// Print the T-type value for an object
public printData() {
System.out.println (x);
}
} // This completes the definition of the
generic class AnyData<T>
The
driver class is programmed below, which creates different types of object of
AnyData<T>.
class SimpleGenericClassTest {
public static void main( String args[] )
{
// A data with the member as
String
AnyData<String> a = new
AnyData<String> (“Java”);
a.printData();
// A data with the member as
integer value
AnyData<Integer> b = new
AnyData<Integer> (123);
b.printData();
// A data with the member as
float value
AnyData<Double> c = new
AnyData<Double> (3.142);
c.printData();
}
}
Example 2.13.
This
program defines a generic class which declares an array to store regular data
such as int, double and String and another array to store another user defined
data type, say Student.
//
Define the user defined Student class
class
Student {
String name; // Name of the students
int marks[3]; // Stores the marks in three subjects
// Constructor for the class Student
Student(String s, int m[ ]) {
name = s;
marks = m;
}
//Defining a method to print student’s
record
void printStudent() {
System.out.println(“Name : “ +
name);
System.out.println(“Scores
: “ + marks[0] + “ “ + marks[1] + “ “
+ marks[2] );
}
} //
End of the class Student
public
class AnyData <T> {
// Two field of generic type T is defined
below
private T x;
// Constructor
public AnyData(T t) {
x = t;
}
// Print the T-type value for an object
public printData() {
System.out.println (x);
}
} // This completes the definition of the
generic class AnyData<T>
The
driver class is programmed below, which creates different types of object of
AnyData<T>.
class SimpleGenericClassTest {
public static void main( String args[] )
{
// A data with the member as
String
AnyData<String> a = new
AnyData<String> (“Java”);
a.printData();
// A data with the member as
integer value
AnyData<Integer> b = new
AnyData<Integer> (123);
b.printData();
// A data with the member as
float value
AnyData<Double> c = new
AnyData<Double> (3.142);
c.printData();
float marks = {89.9, 91.5, 95.6};
Student s = new Student(“Priya”,marks);
// A data with the member as a Student type
AnyData<Student> d = new
AnyData<Student> (s);
d.printData();
}
}
NOTE:
1. You
cannot instantiate an array whose element type is a type parameter. That is
following is invalid.
T a = new T[5];
The reason you can’t create an array of T
is that there is no way for the compiler to know what type of array to actually
create.
2. In parameter type you can not use
primitives type like 'int','char' or
'double'. Only class can be referred as the template data. This is why the
wrapper class for int, float and string are used as Integer, Double and String,
respectively.
Generic class with multiple type parameters
We can
create a generic class with one or more type parameters so that more than one
types of data to be manipulated in a generic program. Following is the syntax, with an example
(see Example 2.14), to define a generic class with two parameters.
Example 2.14.
This
program illustrates a generic class with multiple parameters.
class GC2<T1, T2>
{
T1
obj1; // An object of type T1
T2
obj2; // An object of type T2
GC2(T1
obj1, T2 obj2) {
// Constructor
this.obj1
= obj1;
this.obj2
= obj2;
}
}
public void print() { // A local method in GC2
System.out.println(obj1);
System.out.println(obj2);
}
}
class GC2Test {
// Main class using generic class
public static void main (String[] args) {
GC2
<String, Integer> obj = new GC2<String,
Integer>("GC", 9);
obj.print();
}
}
Example 2.15.
Another
example to illustrate generic class with multiple parameters and generic method
overloading. It also illustrates how arrays can be handled generically.
/* This program illustrates
multiple type parameters in generic
class. */
//
Define the user defined Student class
class
Student {
String name; // Name of the students
int marks[3]; // Stores the marks in three subjects
// Constructor for the class Student
Student(String s, int m[ ]) {
name = s;
marks
= m;
}
//Defining a method to print student’s
record
void printStudent() {
System.out.println(“Name : “ +
name);
System.out.println(“Scores
: “ + marks[0] + “ “ + marks[1] + “ “
+ marks[2] );
}
} //
End of the class Student
//
Defining a generic array with two type parameters
class
GenericArrays<T, S> {
//Declaring an array, which should store any
type T of data
T a[ ];
// Define that the array a[ ] can store one type of data
S b[];
// Define that the array b[ ] can store another type of data
GenericArrays(T x, S y) { // Define a constructor
a = x;
b
= y;
}
T getDataT(int i) {// To return the element
stored in i-th place in the array
return a[i];
}
S getDataS(int i) { //To return the element
stored in i-th place in the array
return b[i];
}
void printData (T t) { // A generic method
to print the elements in array t
for(int i =
0; i < t.length(); i ++)
System.out.print(t.getData(i) +
“ “); //Print the i-th element in t
System.out.println(); // Print a new line
}
void printData(S s){//An overloaded generic
method to print elements in s
for(int i =
0; i < s.length(); i ++)
s[i].printStudent() // Print the i-th student in s
System.out.println(); //
Print a new line
}
void
reverseArray(T t) {//Generic method to reverse the order of elements in t
int front = 0, rear = t.length-1; T temp;
while( front < rear) {
temp = t[rear];
t[rear] = t[front];
t[front] = temp;
front++; rear--;
}
}
void reverseArray(S s){//Generic method to
reverse the order of elements in s
int front = 0, rear =
s.length-1; S temp;
while( front < rear) {
temp = s[rear]
s[rear] = s[front];
s[front] = temp;
front++; rear--;
}
}
} // End of the definition of class GenericArrays
Now, we
are in a position to utilize the class GenericArrays in a program. The main
program is given below.
Class
GenericMultiClassesDemo {
public void static main(String args a[]) {
//Creating an array of String data
String t[ ] = {“A”, “B”, “C”}; // It is an array of String data
//Creating an array of Students’ data
Student s[3]; // It is an array of String data
s[0] = new Student(“Ram”, 86, 66, 96);
s[0] = new Student(“Rahim”, 88, 99,
77);
s[0] = new Student(“John”, 75, 85, 95);
// Store the data into generic arrays
GenericArrays<String, Student>
arrayData = new GenericArrays<String,
Student>(t, s);
//
Printing the data….
arrayData.printData(t); // Printing the array of strings
//Reverse
ordering of data….
arrayData.reverseArray(t);
//
Printing the data….
arrayData.printData(s); // Printing the student’s data
//Reverse
ordering of data….
arrayData.reverseArray(s);
//
Printing the data after reverse ordering….
arrayData.printData(t); // Printing the array of strings
arrayData.printData(s); // Printing the array of students
}
}
The
following example (Example 2.16), illustrates, in general, the usefulness of
generic class and defining a generic class with two parameter types.
Example 2.16.
Consider
the following program, which define a generic class PairData. The PairData class will include two fields whose
values are of any type. Let the generic type be denoted as T and V. Thus, PairData <T, V> is the class with essential members in
it. The class definition of PairData follows the driver class utilizing the PaiaData<T, V> class.
public
class PairData <T, V> {
// Two fields of generic type T and V
private T x;
private V y; // Note: How a field can be defined
generically.
// Constructor
public PairData(T a, V b) {
x = a;
y = b;
}
// Get the T-type value for a pair-data
public T getTvalue() {
return x;
}
// Get the V-type value for a pair-data
public V getVvalue() {
return y;
}
// To print the data member in an object
public void printData() {
System.out.println (getTvalue +
“,” getVvalue);
}
} // This completes the definition of the class
PairData<T, V>
The
driver class is programmed below.
class GenericClassTest {
public static void main( String args[] )
{
// A pair data with both members as
String
PairData<String, String> a = new
PairData<String, String> (“Debasis”,
“Samanta”);
a.printData();
// A pair data with the first member as
String and other as Integer
PairData<String, Integer> b = new
PairData<String, Integer> (“Debasis”,
789);
b.printData();
// A pair data with the first member as
Integer and other as String
PairData<Integer, String> c = new
PairData<Integer, String> (943,
“Samanta”);
c.printData();
// A pair data with the first member as
Integer and other as Double
PairData<Integer, Double> d = new
PairData<Integer, Double> (555,
12.34);
d.printData();
}
}
Bounded
types in generic class definition
If a class A is declared as generic with type parameter <T>,
then object of class can be created of any type. This is fine! But, in
several situations, it may cause error during execution. Following is an
example to illustrate this.
Example 2.17
GenericError<T> {
T[ ]
array; // An array of type T
// Pass the
constructor a reference to an array of
type T.
GenericError (T[
] t) {
array =
t;
}
}
Here, you note that the method doubleValue( ) is well defined for all numeric classes, such as
Integer, Float and Double, which are sub classes
of Number, and Number defines the doubleValue( ) method. Hence, this method
is available to all numeric wrapper classes.
Further, you note that you can create object of the class GenericError with another type
parameter for which there is no method doubleValue()defined. In other words, the compiler does not
have any knowledge about that you are only interested to create objects of numeric
types. Thus, the program reports compile-time error showing that the doubleValue() method is unknown.
To solve this problem, you need some way to tell the compiler that you
intend to pass only numeric types to T. Furthermore, you need some way to
ensure that only numeric types are actually passed.
To handle such situations, Java provides bounded types. When
specifying a type parameter, you can create an upper bound that declares the
superclass from which all type arguments must be derived. This is accomplished
through the use of an extends clause when specifying the
type parameter, as shown here:
<T extends superclass>
This
specifies that T can only be replaced by superclass, or subclasses of
superclass. Thus, superclass defines an inclusive, upper limit.
The
Generic class definition with bounded class is shown in the following.
Example 2.18.
GenericBound<T
extends Numbers > {
T[ ] array; // an array of type T
// Pass the constructor a reference
to an array of type T.
GenericBound (T[ ] t) {
array = t;
}
double average() { // Return type double in all cases
double sum = 0.0;
for(int i=0; i < array.length; i++)
sum += array[i].doubleValue(); // Now, it is okay
return sum / array.length;
}
}
// Driver
class is as follows.
class
GenericBoundDemo {
public static void main(String args[]) {
Integer intArray[] = { 1, 2, 3, 4, 5 };
GenericBound <Integer> intData =
new GenericBound
<Integer>(intArray);
double avgInt = intData.average();
System.out.println("Average is
" + avgInt);
Double doubleArray[] = { 1.1, 2.2, 3.3,
4.4, 5.5 };
GenericBound
<Double> doubleData = new GenericBound
<Double>(doubleArray);
double abgDouble = doubleData.average();
System.out.println("Average is
" + avgDouble);
String strArray[] = { "1",
"2", "3", "4", "5" };
GenericBound <String> strData = new
GenericBound
<String>(strArray);
/*
This won't compile because String is not
a subclass of Number.
double avgStr = strData.average(); // ERROR!
System.out.println("Average is " +
avgStr); */
}
}
Wildcard in
Java Generics
The
question mark symbol (?) is known as the wildcard in generic programming in
Java. Whenever you need to represent an unknown type, you can do that with ?. Java
Generic's wildcards is a mechanism to cast a collection of a certain class. To understand the usefulness of this, let us
consider an example first.
Example 2.19.
The
following class definition is to make a program so that a student’s marks can
be stored in any number format, that is, Integer, Short, Double, Float, Long,
etc.
class
Student <T extends Numbers> {
String name;
T [ ] marks; // To store the marks obtained by a
student
//
The usual constructor for the generic class Student
Student (T [ ] m) {
marks = m;
}
// This method to calculate the total of
marks by a student
double total( ) {
double sum = 0.0;
for(int i = 0; i < marks.length;
i++)
sum += marks[i].doubleValue();
return (sum);
}
// This method compares the marks obtained by
this student with another student
boolean compareMarks(Student<T> otherS) {
if ( total() == otherS.total() )
return true;
return false;
}
} // End of the generic class definition
This
program will compile successfully. Now, let execute the program with different
instances of students, whose marks are stored in their own number format. The
driver class of using the generic class is given below.
/* Driver
class while instantiating the Student generic class with different number
format. */
class
GenericLimitationDemo {
public static void main(String args[]) {
// Marks stored in integer for s1
Integer intMarks1[] = { 44, 55, 33,
66, 77 };
Student<Integer> s1IntMarks=
new Student<Integer>(intMarks1);
System.out.println("Total marks
" + s1IntMarks.total());
// Marks stored in integer for s2
Integer intMarks2[] = { 49, 39, 53, 69
};
Student<Integer> s2IntMarks= new
Student<Integer>(intMarks2);
System.out.println("Total marks
" + s2IntMarks.total());
// Compare marks between s1 and s2
if (s1IntMarks.compareMarks
(s2IntMarks))
System.out.println("Same
marks");
else
System.out.println("Different
marks.");
// Marks stored in double for s3
Double doubleMarks[] = { 43.5, 55.5,
32.5, 66.5, 77.0 };
Student<Double> s3DoubleMarks = new
Student<Double>(doubleMarks);
System.out.println("Total marks "
+ s3Double.total());
// Marks stored in float for s4
Float floatMarks[] = { 50.0F, 40.0F,
60.0F, 65.0F };
Student<Float> s4FloatMarks = new
Student<Float>(floatMarks);
System.out.println("Total marks "
+ s4Float.total());
// Compare marks between s2 and s3
if(s2IntMarks.compareMarks(s3DoubleMarks)) //
Run-time error!
System.out.println("Same
marks");
else
System.out.println("Different
marks.");
// Compare marks between s3 and s4
if (s3DoubleMarks. compareMarks
(s4FloatMarks))
//
Here, also Run-time error!!
System.out.println("Same
marks");
else
System.out.println("Different
marks.");
}
}
Note:
·
There
is no error when s1 is compared with s2; however, the same is not true for s2
and s3 or s3 and s4. The reason is that the si.compareMarks (sj) method works only when the type of the object sj is same as the
invoking object si.
Such a
problem can be solved by using another feature of Java generics called the wildcard
argument. The wildcard argument is specified by the ?, and it just works as the type casting. Thus, with
reference to program in Example 2.16, we have to change the boolean compareMarks (Student <T> t) method with wildcard as boolean compareMarks( Student<?> t). See the modified code (Example 2.20).
Example 2.20
class
Student <T extends Number> {
String name;
T [ ] marks; // To store the marks obtained by a
student
// The usual constructor for the generic
class Student
Student (T [ ] m) {
marks = m;
}
// This method to calculate the total of
marks by a student
double total( ) {
double sum = 0.0;
for(int i = 0; i < marks.length;
i++)
sum += marks[i].doubleValue();
return (sum);
}
/* This method compares the marks obtained
by this student with
another student. */
boolean compareMarks(Student<?> otherS) {
if ( total() == otherS.total() )
return true;
return false;
}
} // End of the generic class definition
The
driver code will remain same as in Example 2.16.
Note:
·
If we
write <? extends Number>, it means any child class of Number, e.g.,
Integer, Float, and Double. Now we can call the method of Number class through
any child class object.
Bounded
wildcard arguments
We have
learnt that how Java’s wildcard feature helps in generic programming. There are
other three different ways that wildcard features are useful.
1.
Upper bound wildcard
These wildcards can be used when you want to write a method that works
on the class where it is defined or any of its sub class.
Syntax:
To declare an upper-bounded wildcard, use
the wildcard character (‘?’), followed by the extends
keyword, followed by its upper bound class name. For example, say A denotes the
upper bound of the class. Then the wildcard uses for the method bounded up to A
is
<type> methodUBA(? extends A) { … }
In
other words, the call of this method is valid for any object of the class A or
any of its child class.
2.
Lower bound wildcard
If you want to limit the call of a method
defined in class A and its parent classes only,
then you can use lower bound wildcard.
Syntax:
It is expressed using the wildcard
character (‘?’), followed by the super keyword, followed by its class name.
<type> methodLBA(? super A) { … }
3.
Unbounded wildcard
These are useful in the following cases:
·
When
writing a method which can be employed using functionality provided in Object
class.
·
When
the code is using methods in the generic class that don’t depend on the type
parameter.
<type> methodNBA(?) { … }
Let us
illustrate the different ways to pass an argument to methods using wildcard in
genetic programming. A simple class hierarchy is given (see the figure given
below), which will be referred in our incoming discussions.
Figure 2.1: Animal class hierarchy
Example 2.21
The
objective of this example is to illustrate how different method can be defined
with different bounder wildcard arguments. A program is given which consists of
the following parts.
1.
Definition
of all the classes as shown in Fig. 2.1.
2.
Declaration
of the generic class, which can be used to store different lists of animals.
3.
Definitions
of different methods to handle objects of different classes in the class
hierarchy.
4.
Driver
class to manipulate the objects of different types.
// Defining all
the classes as in the animal class hierarchy
class Animal {
long lifespan;
float weight;
Animal(long years, float kg) {
lifespan = years;
weight = kg;
}
public void print( ) {
System.out.println(“Maximum longevity: “ + lifespan + “ in years”);
System.out.println(“Maximum weight: “
+ weight + “ in kgs”);
}
} // End of class Animal
class Aquatic extends Animal {
boolean scale; // true: has scale, false: no scale
Aquatic(long years, float kg, boolean
skin) {
super(years, kgs); // Super class constructor
scale = skin;
}
public
void print( ) {
super.print(); // Call the super class method
System.out.println(“Has
scale? “ + scale);
}
} // End of class Aquatic
class Land extends Animal {
short vision; //0 = nocturnal, 1 = only day light, 2 = both
Land(long years, float kg, short vision)
{
super(years, kgs); // Super class constructor
this.vision = vision;
}
} // End of class Land
class Pet extends Land {
String name;
Pet(long years, float kg, short vision,
String name) {
super(years, kgs, vision, name);
// Super class constructor
this.name = name;
}
} // End of class Pet
class Wild extends Land {
float speed; // Maximum running speed in mph
Wild(long years, float kg, short vision,
float speed) {
super(years, kgs, vision, name); // Super class constructor
this.speed = speed;
}
} // End of class Wild
// Defining the
Generic class to maintain lists of different animals
class AnimalWorld<T extends
Animal> {
//Type parameter is limited to
Animal and its sub classes
T [ ] listOfAnimals;
AnimalWorld(T
[ ] list)
// Generic constructor to create a
list of type T
listOfAnimals = list;
}
} // End of the generic class AnimalWorld
// Defining
different methods with different bounds of arguments
class BoundedWildcards {
//Case 1: Unbound wildcard: Any object
can be passed as its argument.
static void vitality(AnimalWorld<?>
animals) {
//To print the vitality of animals in the
list of animals
for(Animal a : animals)
a.print();
System.out.println();
}
// Case 2: Lower bounded wildcard: Any
object of Aquatic or Animal can // be passed as its argument.
static void showSea(AnimalWorld<? super Aquatic> animals) {
//For aquatic or unknown animals
for(Object obj : animals)
obj.print();
// Call the method defined
in Animal/ Aquatic class
System.out.println();
}
// Case 3a: Upper bounded wildcard: Any
object of Land/ Pet/ Wild can // be passed as its argument.
static void showLand(AnimalWorld<? extends Land> animals) {
//For Land or any of its subclasses
for(int i = 0; i <
animals.listOfAnimals.length) {
animals.listOfAnimals[i].print();
// Call the method defined in
Animal class
System.out.println(“Vision : “ +
animals.listOfAnimals[i].vision);
}
System.out.println();
}
// Case 3b: Upper bounded wildcard: Any
object of only Pet class can // be
passed as its argument.
static void
showPet(AnimalWorld<? extends Pet>
animals) {
//For lists of Pet objects only
for(int i = 0; i <
animals.listOfAnimals.length) {
System.out.println(“Pet’s name: “ +
animals.listOfAnimals[i].name);
animals.listOfAnimals[i].print();
// Call the method
defined in Animal class
System.out.println(“Vision : “ +
animals.listOfAnimals[i].vision);
}
System.out.println();
}
// Case 3c: Upper bounded wildcard: Any
object of only Wild class can // be
passed as its argument.
static void
showWild(AnimalWorld<? extends
Wild> animals) {
//For objects of Wild class only
for(int i = 0; i <
animals.listOfAnimals.length) {
animals.listOfAnimals[i].print();
// Call the method
defined in Animal class
System.out.println(“Maximum
running speed: “ +
animals.listOfAnimals[i].speed + “ in mph”);
System.out.println(“Vision : “
+
animals.listOfAnimals[i].vision);
}
System.out.println();
}
} // End of the method definitions in class
BoundedWildcards
// Main Java program utilizing the above-defined classes
class
BoundedWildcardArgumentsDemo {
public static void main(String args[]) {
// Create a list of unknown animals of
class Animal
Animal unknown = new Animal(40,
720);
// An unknown
animal object is created
Animal u [] = {unknown}; // Array of unknown animals
AnimalWorld<Animal> uList = new
AnimalWorld<Animal>(u);
// Place
the unknown into a list
// Create a list of aquatic animals
Aquatic whale = new Aquatic(90,
150000);
// A whale
object is created
Aquatic shark = new Aquatic(400,
2150);
// A shark
object is created
Animal q [] = { whale, shark };
// Array
of aquatic animals
AnimalWorld<Aquatic> qList = new
AnimalWorld<Aquatic>(q);
// Place the aquatics into a list
// Create a list of non-aquatic animals
Land owl = new Land(3, 1, 0);
// A land
owl object is created
Land l [] = { owl }; // An array of land objects is created
AnimalWorld<Land> lList = new
AnimalWorld<Land>(l);
//
Place the pets into a list
// Create a list of pet animals
Pet dog = new Pet(15, 75, 2,
“Prince”);
// A pet dog
object is created
Pet p [] = { new Pet(15, 75, 2,
“Prince”) };
// An array of
pet objects is created
AnimalWorld<Pet> pList = new
AnimalWorld<Pet>(p);
// Place the
pets into a list
// Create a list of wild animals
Wild cheetah = new Land(15, 75,
2);
// A cheetah
object is created
Wild deer = new Land(10, 50,
1);
// A deer object
is created
Wild w [] = { cheetah, deer };
// Array of
non-aquatic animals
AnimalWorld<Wild> wList = new
AnimalWorld<Wild>(w);
// Place the wilds
into a list
// Call the methods and see the
outcomes
// vitality(…) is with unlimited
wildcard argument and
// hence we can pass argument of
any type
vitality (uList); // OK
vitality (qList); //
OK
vitality (lList); // OK
vitality (pList); // OK
vitality (wList); // OK
// showSea(…) is with lower bound
wildcard argument with
// class Aquatic and its super
classes
showSea (uList); // OK
showSea (qList); //
OK
showSea (lList); // Compile-time error
showSea (pList); // Compile-time error
showSea (wList); // Compile-time error
// showLand(…) is with upper bound
wildcard argument with
// class Land and its subclasses
showLand (uList); //
Compile-time error
showLand (qList); //
Compile-time error
showLand (lList); // OK
showLand (pList); // OK
showLand (wList); // OK
// showPet(…) is with upper bound
wildcard argument with
// class Pet and its subclasses
showPet (uList); //
Compile-time error
showPet (qList); //
Compile-time error
showPet (lList); // Compile-time error
showPet (pList); // OK
showPet (wList); // Compile-time error
// showWild(…) is with upper bound
wildcard argument with
// class Wild and its sub classes
showWild (uList); //
Compile-time error
showWild (qList); //
Compile-time error
showWild (lList); // Compile-time error
showWild (pList); // Compile-time error
showWild (wList); // OK
}
}
Note:
·
You
can specify an upper bound for a wildcard, or you can specify a lower bound,
but you cannot specify both.
·
Use
extend wildcard when you want to get values out of a structure and super
wildcard when you put values in a structure. Don’t use wildcard when you get
and put values in a structure.
·
Boundedd
wildcard argument ensure type safety.
·
We
can use a wildcard as a type of a parameter, field, return type, or local
variable. However, it is not allowed to use a wildcard as a type argument for a
generic method invocation, a generic class instance creation, or a supertype.
Hint: When to use what?
In order
to decide which type of wildcard best suits the condition, let's first classify
the type of parameters passed to a method as in and out parameter.
·
in
variable: An in variable provides data
to the code. For example, copy(src, dest). Here src acts as in variable being
data to be copied.
·
out
variable: An out variable holds data updated by the code. For example,
copy(src, dest). Here dest acts as in variable having copied data.
Guidelines
for wildcards.
·
Upper
bound wildcard: If a variable is of in
category, use extends keyword with wildcard.
·
Lower
bound wildcard: If a variable is of out
category, use super keyword with wildcard.
·
Unbounded
wildcard: If a variable can be accessed using Object class method then use an
unbound wildcard.
·
No
wildcard: If code is accessing variable in both in and out category then do not
use wildcards.